#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->appname   = "Extended SmallC Compiler";
    this->aboutinfo = "Author: 徐珑珊\nStudent No: 10195102506\nDate: 2022-10";
    this->filepath  = "";

    this->code_saved();

    this->setWindowTitle(this->appname);
    this->setWindowIcon(QIcon("://resources/gear.png"));
    this->setWindowState(Qt::WindowMaximized);

    // set indent
    ui->plainTextEdit_code->setTabStopDistance(4 * ui->plainTextEdit_code->fontMetrics().averageCharWidth());

    // connect
    // 1. menubar
    // 1.1. file
    connect(ui->actionNew,          &QAction::triggered, this, &MainWindow::actionNew_triggered);
    connect(ui->actionOpen,         &QAction::triggered, this, &MainWindow::actionOpen_triggered);
    connect(ui->actionSave_file,    &QAction::triggered, this, &MainWindow::actionSave_file_triggered);
    connect(ui->actionSave_file_as, &QAction::triggered, this, &MainWindow::actionSave_file_as_triggered);
    // 1.2. run
    connect(ui->actionCompile,      &QAction::triggered, this, &MainWindow::actionCompile_triggered);
    connect(ui->actionRun,          &QAction::triggered, this, &MainWindow::actionRun_triggered);
    connect(ui->actionStep,         &QAction::triggered, this, &MainWindow::actionStep_triggered);
    // 1.3. help
    connect(ui->actionAbout,        &QAction::triggered, this, &MainWindow::actionAbout_triggered);

    // 2. code panel
    // 2.1. input
    connect(ui->toolButton_new,     &QToolButton::clicked, this, &MainWindow::actionNew_triggered);
    connect(ui->toolButton_open,    &QToolButton::clicked, this, &MainWindow::actionOpen_triggered);
    connect(ui->toolButton_save,    &QToolButton::clicked, this, &MainWindow::actionSave_file_triggered);
    connect(ui->toolButton_saveas,  &QToolButton::clicked, this, &MainWindow::actionSave_file_as_triggered);
    connect(ui->toolButton_compile, &QToolButton::clicked, this, &MainWindow::actionCompile_triggered);
    connect(ui->plainTextEdit_code, &QPlainTextEdit::textChanged, this, &MainWindow::code_changed);
    // 2.2. instr
    connect(ui->toolButton_run,     &QToolButton::clicked, this, &MainWindow::actionRun_triggered);
    connect(ui->toolButton_step,    &QToolButton::clicked, this, &MainWindow::actionStep_triggered);
    // 2.3. output panel
    connect(ui->toolButton_clear,   &QToolButton::clicked, this, &MainWindow::clear_output);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::highlight(int lineno){
    // 当前行高亮
    QList<QTextEdit::ExtraSelection> extraSelections;
    QTextEdit::ExtraSelection selection;

    // 设置高亮颜色
    QColor lineColor;
    lineColor.setRgb(0, 0, 0);
    selection.format.setBackground(lineColor);
    selection.format.setProperty(QTextFormat::FullWidthSelection, true);

    // 调整光标位置
    QTextCursor currentCursor(ui->textEdit_instr->textCursor());
    currentCursor.movePosition(QTextCursor::Start);
    currentCursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, lineno);// 移动副本光标

    ui->textEdit_instr->setTextCursor(currentCursor);       // 更新可见光标

    selection.cursor = ui->textEdit_instr->textCursor();    // 新光标位置
    selection.cursor.clearSelection();                      // 通过设置锚到光标位置清除当前的选择
    extraSelections.append(selection);
    ui->textEdit_instr->setExtraSelections(extraSelections);
}

// ///////////////////////////////////////////////////////////
// MenuBar
// ///////////////////////////////////////////////////////////

//
// create a new small C file in the editor
//
void MainWindow::actionNew_triggered()
{
    // check if the code editor has been changed
    if ( this->codechanged ){   // changed, save the changes
        int click = QMessageBox::information(this,
                                             this->appname,
                                             "Do you want to save the current code file?",
                                             QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
        if ( click == QMessageBox::Yes ){           // save the code editor
            actionSave_file_triggered();
        }else if ( click == QMessageBox::Cancel ){  // do nothing
            return;
        }
    }
    // empty the code editor
    this->filepath.clear();
    ui->plainTextEdit_code->clear();
    this->code_saved();
}

//
// open a new small C file
//
void MainWindow::actionOpen_triggered()
{
    // check if the code editor has been changed
    if ( this->codechanged ){   // changed, save the changes
        int click = QMessageBox::information(this,
                                             this->appname,
                                             "Do you want to save the current code file?",
                                             QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
        if ( click == QMessageBox::Yes ){           // save the code editor
            actionSave_file_triggered();
        }else if ( click == QMessageBox::Cancel ){  // do nothing
            return;
        }
    }
    // open a new file
    QString fp = QFileDialog::getOpenFileName(this, tr("Browse..."),"",tr("Files (*.*)"));
    if ( fp.isEmpty() ) return;
    this->filepath = fp;    // save the file path
    QFile file(this->filepath);
    if ( ! file.exists() ){
        QMessageBox::critical(this, this->appname, "Invalid file path!");
        return;
    }
    // open the file
    if ( ! file.open(QIODevice::ReadOnly|QIODevice::Text) ){
        QMessageBox::critical(this, this->appname, "Cannot open this file!");
        return;
    }
    // 把文件里的内容拷贝到code edit中
    ui->plainTextEdit_code->setPlainText(file.readAll());
    file.close();
    this->code_saved();
}

//
// save file
//
void MainWindow::actionSave_file_triggered()
{
    // no filepath specified, select a path
    if ( this->filepath.isEmpty() ){
        actionSave_file_as_triggered();
        return;
    }
    // 把文件保存到filepath中
    QFile file(this->filepath);
    if ( ! file.exists() ){
        QMessageBox::critical(this, this->appname, "Invalid file path!");
        return;
    }
    if ( ! file.open(QIODevice::WriteOnly | QIODevice::Text) ){
        QMessageBox::critical(this, this->appname, "Cannot open the file!");
        actionSave_file_as_triggered();
        return;
    }
    QString str = ui->plainTextEdit_code->toPlainText();
    QByteArray strBytes = str.toUtf8(); // 转换为字节数组
    file.write(strBytes, strBytes.length());
    file.close();
    this->code_saved();
}

//
// save file as
//
void MainWindow::actionSave_file_as_triggered()
{
    QString fp = QFileDialog::getSaveFileName(this, tr("Save..."),"",tr("Files (*.*)"));
    if ( fp.isEmpty() ) return;
    QFile file(fp);
    if ( ! file.open(QIODevice::WriteOnly | QIODevice::Text) ){
        QMessageBox::critical(this, this->appname, "Cannot open the file!");
        return;
    }
    QString str = ui->plainTextEdit_code->toPlainText();
    QByteArray strBytes = str.toUtf8(); // 转换为字节数组
    file.write(strBytes, strBytes.length());
    file.close();
    this->filepath = fp;
    this->code_saved();
}

//
// show author information
//
void MainWindow::actionAbout_triggered()
{
    QMessageBox::information(this, this->appname, this->aboutinfo);
}

//
// compile triggered
//
void MainWindow::actionCompile_triggered(){
    // 输出提示语句
    ui->textEdit_output->append("Compiling "+this->filepath+" ...");
    // 清空instr, data
    ui->textEdit_instr->setText("");
    ui->textEdit_table->setText("");
    ui->textEdit_stack->setText("");
    // 获取代码
    string in = ui->plainTextEdit_code->toPlainText().toUtf8().data();
    string out, instr, table;
    // 开启线程进行编译
    std::thread com(compile, &in, &out, &instr, &table);

    // 等待编译结束，回收线程
    com.join();

    // 指令指针置为0
    p = 0;
    // 获取编译结果
    ui->textEdit_output->append(QString::fromUtf8(out.c_str()));
    if ( instr.length() > 0 )
        ui->textEdit_instr->setText(QString::fromUtf8(instr.c_str()));
    if ( table.length() > 0 )
        ui->textEdit_table->setText(QString::fromUtf8(table.c_str()));
}

//
// run triggered, interpret
//
void MainWindow::actionRun_triggered(){
    // 输出提示语句
    ui->textEdit_output->append("Running "+this->filepath+" ...");
    // 获取执行结果
    string res;

    // 开启线程进行解释
    isComplete = 0;

    sem_init(&readdata, 0, 0);
    sem_init(&dataready, 0, 0);
    std::thread com(interpret, &res);

    while ( 1 ){
        sem_wait(&readdata);
        if ( isComplete == 1 )          // 程序已经结束，不需要读数据了
            break;
        indata = QInputDialog::getInt(this, "Input an integer", "Read");
//        qDebug("indata = %d", indata);
        sem_post(&dataready);
    }

    // 回收线程
    com.join();

    ui->textEdit_output->append(QString::fromUtf8(res.c_str()));
}

//
// run by step triggered
//
void MainWindow::actionStep_triggered(){
    highlight(p);

    // 第一次
    if ( p == 0 ){
        ui->textEdit_output->append("Running "+this->filepath+" by step ...");
    }

    string res, stack;

    // 开启线程进行解释
    isComplete = 0;

    sem_init(&readdata, 0, 0);
    sem_init(&dataready, 0, 0);
    std::thread com(interpret_by_step, &res, &stack);

    // 单步执行一条指令只可能读一次
    sem_wait(&readdata);

    if ( isComplete == 0 ){             // 还没执行完毕，等着读数据
        indata = QInputDialog::getInt(this, "Input an integer", "Read");
//        qDebug("indata = %d", indata);
        sem_post(&dataready);
        sem_wait(&readdata);            // 等待线程结束
    }

    // 回收线程
    com.join();

    if ( res.length() > 0 )
        ui->textEdit_output->append(QString::fromUtf8(res.c_str()));
    ui->textEdit_stack->setText(QString::fromUtf8(stack.c_str()));
}

//
// code changed
//
void MainWindow::code_changed(){
    this->codechanged = true;
    ui->label_code->setText("Code ("+this->filepath+"*)");
}

//
// code saved
//
void MainWindow::code_saved(){
    this->codechanged = false;
    ui->label_code->setText("Code ("+this->filepath+")");
}

//
// clear output
//
void MainWindow::clear_output(){
    ui->textEdit_output->clear();
}
